package com.mate.cloud.lottery.service.impl;


import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Maps;
import com.mate.cloud.lottery.constants.LotteryConstants;
import com.mate.cloud.lottery.constants.LotteryExceptionCodeEnum;
import com.mate.cloud.lottery.constants.RedisKeyManager;
import com.mate.cloud.lottery.domain.Lottery;
import com.mate.cloud.lottery.domain.LotteryItem;
import com.mate.cloud.lottery.dto.DoDrawDTO;
import com.mate.cloud.lottery.events.InitPrizeToRedisEvent;
import com.mate.cloud.lottery.exception.LotteryBusinessException;
import com.mate.cloud.lottery.exception.UnRewardException;
import com.mate.cloud.lottery.mapper.LotteryItemMapper;
import com.mate.cloud.lottery.mapper.LotteryMapper;
import com.mate.cloud.lottery.service.AsyncLotteryRecordTask;
import com.mate.cloud.lottery.service.ILotteryService;
import com.mate.cloud.lottery.service.stock.AbstractRewardProcessor;
import com.mate.cloud.lottery.service.stock.RewardContext;
import com.mate.cloud.lottery.service.stock.RewardProcessor;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author MI
 * @since 2025-01-24
 */
@Slf4j
@Service
public class LotteryServiceImpl extends ServiceImpl<LotteryMapper, Lottery> implements ILotteryService {

    @Resource
    LotteryMapper lotteryMapper;
    @Resource
    RedisTemplate redisTemplate;
    @Resource
    LotteryItemMapper lotteryItemMapper;
    @Resource
    ApplicationContext applicationContext;
    @Resource
    AsyncLotteryRecordTask asyncLotteryRecordTask;

    private static final int mulriple = 10000;

    @Override
    public void doDraw(DoDrawDTO drawDto) {
        RewardContext context = new RewardContext();
        LotteryItem lotteryItem = null;
        try {
            CountDownLatch countDownLatch = new CountDownLatch(1);
            // 判断活动有效性
            Lottery lottery = checkLottery(drawDto);
            // 发布事件，加载奖品信息
            applicationContext.publishEvent(new InitPrizeToRedisEvent(this, lottery.getId(), countDownLatch));
            // 开始抽奖
            lotteryItem = doPlay(lottery);
            // 等待奖品初始化（最多3秒）
            boolean awaitSuccess = countDownLatch.await(3, TimeUnit.SECONDS);
            if (!awaitSuccess) {
                log.error("奖品初始化超时，lotteryId: {}", lottery.getId());
                throw new LotteryBusinessException(LotteryExceptionCodeEnum.PRIZE_INIT_TIMEOUT.getCode());
            }

            // 获取奖品类型（带校验）
            String key = RedisKeyManager.getLotteryPrizeRedisKey(lottery.getId(), lotteryItem.getPrizeId());
            Object prizeTypeObj = redisTemplate.opsForHash().get(key, "prizeType");
            if (prizeTypeObj == null || !StringUtils.isNumeric(prizeTypeObj.toString())) {
                log.error("Redis 中 prizeType 无效，key: {}, value: {}", key, prizeTypeObj);
                throw new LotteryBusinessException(LotteryExceptionCodeEnum.PRIZE_TYPE_INVALID.getCode());
            }
            int prizeType = Integer.parseInt(prizeTypeObj.toString());

            // 准备上下文
            context.setLottery(lottery);
            context.setLotteryItem(lotteryItem);
            context.setAccountIp(drawDto.getAccountIp());
            context.setKey(key);

            // 获取处理器并发放奖励（带 null 校验）
            RewardProcessor processor = AbstractRewardProcessor.rewardProcessorMap.get(prizeType);
            if (processor == null) {
                log.error("未找到奖品类型 [{}] 的处理器", prizeType);
                throw new LotteryBusinessException(LotteryExceptionCodeEnum.REWARD_PROCESSOR_NOT_FOUND.getCode());
            }
            processor.doReward(context);

        } catch (UnRewardException u) {
            // 处理未中奖场景
            if (lotteryItem == null) {
                log.error("未中奖时，lotteryItem 为 null");
                throw new LotteryBusinessException(LotteryExceptionCodeEnum.LOTTERY_ITEM_NULL.getCode());
            }
            String defaultKey = RedisKeyManager.getDefaultLotteryPrizeRedisKey(lotteryItem.getLotteryId());
            context.setKey(defaultKey);

            // 获取默认奖项（带 null 校验）
            lotteryItem = (LotteryItem) redisTemplate.opsForValue().get(RedisKeyManager.getDefaultLotteryItemRedisKey(lotteryItem.getLotteryId()));
            if (lotteryItem == null) {
                log.error("默认奖项不存在，lotteryId: {}", lotteryItem.getLotteryId());
                throw new LotteryBusinessException(LotteryExceptionCodeEnum.DEFAULT_PRIZE_NOT_FOUND.getCode());
            }
            context.setLotteryItem(lotteryItem);

            // 发放“谢谢参与”奖励（带 null 校验）
            Integer thankType = LotteryConstants.PrizeTypeEnum.THANK.getValue();
            RewardProcessor thankProcessor = AbstractRewardProcessor.rewardProcessorMap.get(thankType);
            if (thankProcessor == null) {
                log.error("未找到默认奖项处理器，类型: {}", thankType);
                throw new LotteryBusinessException(LotteryExceptionCodeEnum.REWARD_PROCESSOR_NOT_FOUND.getCode());
            }
            thankProcessor.doReward(context);

        } catch (InterruptedException e) {
            log.error("抽奖过程被中断", e);
            Thread.currentThread().interrupt(); // 恢复中断状态
            throw new LotteryBusinessException(LotteryExceptionCodeEnum.SYSTEM_ERROR.getCode());
        }

        // 拼接返回数据
        drawDto.setLevel(lotteryItem.getLevel());
        drawDto.setPrizeName(context.getPrizeName());
        drawDto.setPrizeId(context.getPrizeId());
    }

    /**
     * 校验当前活动的有效信息
     *
     * @param drawDto
     * @return
     */
    private Lottery checkLottery(DoDrawDTO drawDto) {
        Lottery lottery;
        Object lotteryJsonStr = redisTemplate.opsForValue().get(RedisKeyManager.getLotteryRedisKey(drawDto.getLotteryId()));
        if (null != lotteryJsonStr) {
            lottery = JSON.parseObject(JSON.toJSONString(lotteryJsonStr), Lottery.class);
        } else {
            lottery = lotteryMapper.selectById(drawDto.getLotteryId());
        }
        if (lottery == null) {
            throw new LotteryBusinessException(LotteryExceptionCodeEnum.LOTTER_NOT_EXIST.getCode());
        }
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(lottery.getStartTime()) || now.isAfter(lottery.getEndTime())) {
            throw new LotteryBusinessException(LotteryExceptionCodeEnum.LOTTER_FINISH.getCode());
        }
        return lottery;
    }

    //执行抽奖
    private LotteryItem doPlay(Lottery lottery) {
        LotteryItem lotteryItem = null;
        QueryWrapper<LotteryItem> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("lottery_id", lottery.getId());
        Object lotteryItemsObj = redisTemplate.opsForValue().get(RedisKeyManager.getLotteryItemRedisKey(lottery.getId()));
        List<LotteryItem> lotteryItems;

        //说明还未加载到缓存中，同步从数据库加载，并且异步将数据缓存
        if (lotteryItemsObj == null) {
            lotteryItems = lotteryItemMapper.selectList(queryWrapper);
        } else {
            lotteryItems = JSON.parseArray(JSON.toJSONString(lotteryItemsObj), LotteryItem.class);
        }
        //奖项数据未配置
        if (lotteryItems.isEmpty()) {
            throw new LotteryBusinessException(LotteryExceptionCodeEnum.LOTTER_ITEM_NOT_INITIAL.getCode());
        }
        int lastScope = 0;
        Collections.shuffle(lotteryItems);
        Map<Integer, int[]> awardItemScope = Maps.newHashMap();
        //item.getPercent=0.05 = 5%
        for (LotteryItem item : lotteryItems) {
            int currentScope = lastScope + new BigDecimal(item.getPercent().floatValue()).multiply(new BigDecimal(mulriple)).intValue();
            awardItemScope.put(item.getId(), new int[]{lastScope + 1, currentScope});
            lastScope = currentScope;
        }
        int luckyNumber = new Random().nextInt(mulriple);
        int luckyPrizeId = 0;
        if (!awardItemScope.isEmpty()) {
            Set<Map.Entry<Integer, int[]>> set = awardItemScope.entrySet();
            for (Map.Entry<Integer, int[]> entry : set) {
                if (luckyNumber >= entry.getValue()[0] && luckyNumber <= entry.getValue()[1]) {
                    luckyPrizeId = entry.getKey();
                    break;
                }
            }
        }
        for (LotteryItem item : lotteryItems) {
            if (item.getId().intValue() == luckyPrizeId) {
                lotteryItem = item;
                break;
            }
        }
        return lotteryItem;
    }
}
